﻿using System;
using System.Diagnostics;
using System.Threading;
using Pfz.Threading.Cooperative;

namespace Pfz.Threading.Unsafe
{
	/// <summary>
	/// Class that creates a Timer that runs an action at given intervals.
	/// It is high precision (and cpu intensive). It tries to compensate slowdowns, but never stacks.
	/// </summary>
	public sealed class UnsafeHighPrecisionTimer:
		IAdvancedDisposable
	{
		private bool _wasDisposed;

		/// <summary>
		/// Creates a new timer that will run the given action at the given interval milliseconds.
		/// If exclusiveThreadName is not null, then a new Thread will be created. Otherwise, a 
		/// UnlimitedThreadPool thread will be used.
		/// </summary>
		public UnsafeHighPrecisionTimer(Action action, double interval, string exclusiveThreadName=null):
			this(action, TimeSpan.FromMilliseconds(interval), exclusiveThreadName)
		{
		}

		/// <summary>
		/// Creates a new timer that will run the given action at the given interval milliseconds.
		/// If exclusiveThreadName is not null, then a new Thread will be created. Otherwise, a 
		/// UnlimitedThreadPool thread will be used.
		/// </summary>
		public UnsafeHighPrecisionTimer(Action action, TimeSpan interval, string exclusiveThreadName=null)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			if (interval <= TimeSpan.Zero)
				throw new ArgumentOutOfRangeException("interval must be greater than 0.");

			Action = action;
			_interval = interval;

			if (exclusiveThreadName == null)
				UnlimitedThreadPool.Run(_Run);
			else
			{
				Thread thread = new Thread(_Run);
				thread.Name = exclusiveThreadName;
				thread.Start();
			}
		}

		/// <summary>
		/// Releases the Timer thread immediatelly.
		/// Note that, if such thread is already running, it will continue to the end of the action,
		/// and only then will be returned to the pool, but the Timer will return immediatelly.
		/// </summary>
		public void Dispose()
		{
            if (_wasDisposed)
				return;

			_wasDisposed = true;
		}

		/// <summary>
		/// Throws an exception if this object was already disposed.
		/// </summary>
		private void _CheckUndisposed()
		{
			if (WasDisposed)
				throw new ObjectDisposedException(GetType().FullName);
		}

		/// <summary>
		/// Gets a value indicating if this object was already disposed.
		/// </summary>
		public bool WasDisposed
		{
			get
			{
				return _wasDisposed;
			}
		}

		/// <summary>
		/// Gets the action that's run by this timer.
		/// </summary>
		public Action Action { get; private set; }

        private TimeSpan _interval;
		/// <summary>
		/// Gets or sets the interval in which the action is run.
		/// </summary>
		public TimeSpan Interval
        {
            get
            {
                return _interval;
            }
            set
            {
                if (value <= TimeSpan.Zero)
                    throw new ArgumentException("Interval must be greater than zero.");

                _interval = value;
            }
        }

		private void _Run()
		{
			var currentThread = Thread.CurrentThread;

			Stopwatch stopwatch = null;
			try
			{
				TimeSpan nextInterval = _interval;
				stopwatch = new Stopwatch();
				while(true)
				{
					stopwatch.Reset();
					stopwatch.Start();
					Action();

					if(stopwatch.Elapsed < nextInterval)
					{
						currentThread.IsBackground = true;

						while(stopwatch.Elapsed < nextInterval)
						{
							Thread.MemoryBarrier();

							if (_wasDisposed)
								return;

							Thread.Yield();
						}

						currentThread.IsBackground = false;
					}
					else
					{
						Thread.MemoryBarrier();

						if (_wasDisposed)
							return;
					}

					stopwatch.Stop();

					TimeSpan lastActionTime = stopwatch.Elapsed - nextInterval;
					nextInterval = _interval - lastActionTime;
					if (nextInterval < TimeSpan.Zero)
						nextInterval = TimeSpan.Zero;
				}
			}
			finally
			{
				currentThread.IsBackground = false;

				if (stopwatch != null)
					stopwatch.Stop();
			}
		}
	}
}
